﻿using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;


namespace Nirvana
{
    /// <summary>
    /// 粒子规则，限制粒子最大数量，关闭预热
    /// 注意：
    ///     规则文件必须存放在资源的父目录或同一级目录下。便于不同资源模块的管理。
    /// </summary>
    [CreateAssetMenu(fileName = "@ParticleSystemImportRule", menuName = "Nirvana/Asset/ParticleSystemImportRule")]
    public class ParticleSystemImportRule : ScriptableObject
    {
        /// <summary>
        /// 指定检测规则的资源
        /// </summary>
        [Tooltip("通过哪些资源.")]
        [SerializeField]
        private ListStringVariate includeRegexs;

        /// <summary>
        /// 忽略规则检测
        /// </summary>
        [Tooltip("排除哪些资源.")]
        [SerializeField]
        private ListStringVariate excludeRegexs;

        /// <summary>
        /// 检测粒子预热开启状态
        /// 粒子系统的Prewarm操作会在使用时的第一帧中造成相对集中的CPU耗时，
        /// 很可能会造成运行时局部卡顿，建议考虑是否确实需要开启该选项，如果可以不用则将其关闭
        /// </summary>
        [Tooltip("Check ParticleSystem PreWarm State")]
        [SerializeField]
        private BoolVariate checkPrvWarm;

        /// <summary>
        /// 粒子最大预热时间
        /// </summary>
        [SerializeField]
        private FloatVariate maxPrvwarmTime;

        /// <summary>
        /// 最大粒子数
        /// </summary>
        [SerializeField]
        private IntVariate maxParticleCount;

        [Tooltip("The asset check report.")]
        [SerializeField]
        [HideInInspector]
        private AssetCheckReport report;
        public AssetCheckReport Report { get { return report; } }

        /// <summary>
        /// 返回当前配置脚本的存放目录
        /// </summary>
        /// <returns></returns>
        public string GetDirectorName()
        {
            string assetpath = AssetDatabase.GetAssetPath(this);
            return Path.GetDirectoryName(assetpath);
        }

        public bool IsMatchRegex(string assetPath)
        {
            if (!includeRegexs.IsHasValue()) return false;

            string ruleDirname = GetDirectorName();
            ruleDirname = ruleDirname.Replace('/', '\\');
            if (ruleDirname[ruleDirname.Length - 1] != Path.DirectorySeparatorChar) ruleDirname += Path.DirectorySeparatorChar;

            string filename = assetPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
            //同一父目录下才检测规则
            if (!filename.StartsWith(ruleDirname, System.StringComparison.OrdinalIgnoreCase)) return false;

            //指定包含的规则
            var temparray = this.includeRegexs.GetValue().ToArray();
            Regex[] regexs = null;
            if (temparray != null && temparray.Length > 0)
            {
                regexs = new Regex[temparray.Length];
                for (int i = 0; i < temparray.Length; i++) regexs[i] = new Regex(temparray[i]);
            }

            //需要忽略的规则
            temparray = this.excludeRegexs.GetValue().ToArray();
            Regex[] exRegexs = null;
            if (temparray != null && temparray.Length > 0)
            {
                exRegexs = new Regex[temparray.Length];
                for (int i = 0; i < temparray.Length; i++) exRegexs[i] = new Regex(temparray[i]);
            }

            string input = DirectoryUtil.RelativePath(GetDirectorName(), assetPath);
            if (regexs != null)
            {
                bool flag = false;
                foreach (Regex r in regexs)
                {
                    if (r.IsMatch(input))
                    {
                        flag = true;
                    }
                }
                if (!flag)
                {
                    return false;
                }
            }
            if (exRegexs != null)
            {
                bool flag2 = false;
                foreach (Regex r in exRegexs)
                {
                    if (r.IsMatch(input))
                    {
                        flag2 = true;
                        break;
                    }
                }
                if (flag2)
                {
                    return false;
                }
            }
            return true;
        }

        private bool CheckParticleSystem(ParticleSystem[] particle_systems, ref AssetCheckReport report)
        {
            float prvWarnTime = 0f;
            for (int i = 0; i < particle_systems.Length; i++)
            {
                ParticleSystem p = particle_systems[i];
                if (checkPrvWarm.IsHasValue())
                {
                    if (p.main.prewarm)
                    {
                        prvWarnTime = maxPrvwarmTime.IsHasValue() ? maxPrvwarmTime.GetValue() : 0;
                        if (p.main.duration > prvWarnTime)
                        {
                            report.ShowWarnInfo("The ParticleSystem <b><color=orange>{0}</color></b> is open prewarm, and warnTime is <b><color=orange>{1}</color></b> is more than {2}", p.name, p.main.duration, prvWarnTime);
                        }
                    }
                }

                if (maxParticleCount.IsHasValue())
                {
                    if (p.main.maxParticles > maxParticleCount.GetValue())
                    {
                        report.ShowWarnInfo("The ParticleSystem <b><color=orange>{0}</color></b> max particle count is <b><color=orange>{1}</color></b>, is more than {2}.",
                            p.name,
                            p.main.maxParticles,
                            maxParticleCount.GetValue()
                        );
                    }

                }
            }
            return true;
        }

        public void CheckParticleSystemRule()
        {
            if (this.report == null) this.report = new AssetCheckReport();
            else this.report.Clear();

            string findpath = GetDirectorName();

            string[] prefabGUIDArray = AssetDatabase.FindAssets("t:prefab", new string[] { findpath });
            for (int i = 0; i < prefabGUIDArray.Length; i++)
            {
                string path = AssetDatabase.GUIDToAssetPath(prefabGUIDArray[i]);
                if (!IsMatchRegex(path))
                {
                    continue;
                }

                var asset = AssetDatabase.LoadAssetAtPath<GameObject>(path);
                if (asset == null)
                {
                    continue;
                }
                var cancel = EditorUtility.DisplayCancelableProgressBar("Check ParticleSystem", "Prefab -> " + asset.name, i * 1.0f / prefabGUIDArray.Length);
                if (cancel)
                {
                    break;
                }
                report.assetpath = path;
                report.objectname = asset.name;
                report.hierarchyPath = null;

                ParticleSystem[] particle_systems = asset.GetComponentsInChildren<ParticleSystem>(true);
                CheckParticleSystem(particle_systems, ref report);
            }
            EditorUtility.ClearProgressBar();

        }

        public void CloseParticleSystemPrvwarm()
        {
            if (this.report != null) this.report.Clear();
            string findpath = GetDirectorName();

            if (!maxPrvwarmTime.IsHasValue())
            {
                report.assetpath = "";
                report.objectname = "";
                report.hierarchyPath = null;
                report.ShowErrorInfo("The ParticleSystem Rule Must Be Open maxPrvwarmTime Toggle!!!");
                return;
            }

            float prvWarnTime = Mathf.Max(0, maxPrvwarmTime.GetValue());

            bool isChange = false;

            string[] prefabGUIDArray = AssetDatabase.FindAssets("t:prefab", new string[] { findpath });
            for (int i = 0; i < prefabGUIDArray.Length; i++)
            {
                string path = AssetDatabase.GUIDToAssetPath(prefabGUIDArray[i]);
                if (!IsMatchRegex(path))
                {
                    continue;
                }

                var asset = AssetDatabase.LoadAssetAtPath<GameObject>(path);
                if (asset == null)
                {
                    continue;
                }

                var cancel = EditorUtility.DisplayCancelableProgressBar("Close ParticleSystem PreWarm", "Prefab -> " + asset.name, i * 1.0f / prefabGUIDArray.Length);
                if (cancel)
                {
                    break;
                }


                ParticleSystem[] particle_systems = asset.GetComponentsInChildren<ParticleSystem>(true);
                foreach (var p in particle_systems)
                {
                    ParticleSystem.MainModule main_module = p.main;
                    if (main_module.prewarm)
                    {
                        if (p.main.duration > prvWarnTime)
                        {
                            main_module.prewarm = false;
                            isChange = true;
                            EditorUtility.SetDirty(p);
                        }
                    }
                }
                if (isChange)
                {
                    isChange = false;
                    PrefabUtility.SavePrefabAsset(asset);
                }
            }
            EditorUtility.ClearProgressBar();

            AssetDatabase.SaveAssets();
        }


        public void SetParticleSystemMaxCount()
        {
            if (this.report != null) this.report.Clear();
            string findpath = GetDirectorName();

            if (this.maxParticleCount.IsHasValue() == false) return;
            bool isChange = false;
            string[] prefabGUIDArray = AssetDatabase.FindAssets("t:prefab", new string[] { findpath });
            for (int i = 0; i < prefabGUIDArray.Length; i++)
            {
                string path = AssetDatabase.GUIDToAssetPath(prefabGUIDArray[i]);
                if (!IsMatchRegex(path))
                {
                    continue;
                }

                var asset = AssetDatabase.LoadAssetAtPath<GameObject>(path);
                if (asset == null)
                {
                    continue;
                }

                var cancel = EditorUtility.DisplayCancelableProgressBar("Set ParticleSystem Max Count", "Prefab -> " + asset.name, i * 1.0f / prefabGUIDArray.Length);
                if (cancel)
                {
                    break;
                }


                ParticleSystem[] particle_systems = asset.GetComponentsInChildren<ParticleSystem>(true);
                foreach (var p in particle_systems)
                {
                    ParticleSystem.MainModule main_module = p.main;
                    if (main_module.maxParticles > this.maxParticleCount.GetValue() && this.maxParticleCount.GetValue() > 30)
                    {
                        isChange = true;
                        main_module.maxParticles = this.maxParticleCount.GetValue();
                        EditorUtility.SetDirty(p);
                    }
                }
                if (isChange)
                {
                    isChange = false;
                    PrefabUtility.SavePrefabAsset(asset);
                }
            }
            EditorUtility.ClearProgressBar();

            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }
    }
}

